/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.support;

import java.util.LinkedHashSet;
import java.util.Set;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

/**
 * Abstract base class for {@code MessageSource} implementations based on
 * resource bundle conventions, such as {@link ResourceBundleMessageSource}
 * and {@link ReloadableResourceBundleMessageSource}. Provides common
 * configuration methods and corresponding semantic definitions.
 *
 * @author Juergen Hoeller
 * @see ResourceBundleMessageSource
 * @see ReloadableResourceBundleMessageSource
 * @since 4.3
 */
public abstract class AbstractResourceBasedMessageSource extends AbstractMessageSource {

    private final Set<String> basenameSet = new LinkedHashSet<>(4);

    @Nullable
    private String defaultEncoding;

    private boolean fallbackToSystemLocale = true;

    private long cacheMillis = -1;


    /**
     * Set a single basename, following the basic ResourceBundle convention
     * of not specifying file extension or language codes. The resource location
     * format is up to the specific {@code MessageSource} implementation.
     * <p>Regular and XMl properties files are supported: e.g. "messages" will find
     * a "messages.properties", "messages_en.properties" etc arrangement as well
     * as "messages.xml", "messages_en.xml" etc.
     *
     * @param basename the single basename
     * @see #setBasenames
     * @see org.springframework.core.io.ResourceEditor
     * @see java.util.ResourceBundle
     */
    public void setBasename(String basename) {
        setBasenames(basename);
    }

    /**
     * Set an array of basenames, each following the basic ResourceBundle convention
     * of not specifying file extension or language codes. The resource location
     * format is up to the specific {@code MessageSource} implementation.
     * <p>Regular and XMl properties files are supported: e.g. "messages" will find
     * a "messages.properties", "messages_en.properties" etc arrangement as well
     * as "messages.xml", "messages_en.xml" etc.
     * <p>The associated resource bundles will be checked sequentially when resolving
     * a message code. Note that message definitions in a <i>previous</i> resource
     * bundle will override ones in a later bundle, due to the sequential lookup.
     * <p>Note: In contrast to {@link #addBasenames}, this replaces existing entries
     * with the given names and can therefore also be used to reset the configuration.
     *
     * @param basenames an array of basenames
     * @see #setBasename
     * @see java.util.ResourceBundle
     */
    public void setBasenames(String... basenames) {
        this.basenameSet.clear();
        addBasenames(basenames);
    }

    /**
     * Add the specified basenames to the existing basename configuration.
     * <p>Note: If a given basename already exists, the position of its entry
     * will remain as in the original set. New entries will be added at the
     * end of the list, to be searched after existing basenames.
     *
     * @see #setBasenames
     * @see java.util.ResourceBundle
     * @since 4.3
     */
    public void addBasenames(String... basenames) {
        if (!ObjectUtils.isEmpty(basenames)) {
            for (String basename : basenames) {
                Assert.hasText(basename, "Basename must not be empty");
                this.basenameSet.add(basename.trim());
            }
        }
    }

    /**
     * Return this {@code MessageSource}'s basename set, containing entries
     * in the order of registration.
     * <p>Calling code may introspect this set as well as add or remove entries.
     *
     * @see #addBasenames
     * @since 4.3
     */
    public Set<String> getBasenameSet() {
        return this.basenameSet;
    }

    /**
     * Set the default charset to use for parsing properties files.
     * Used if no file-specific charset is specified for a file.
     * <p>Default is none, using the {@code java.util.Properties}
     * default encoding: ISO-8859-1.
     * <p>Only applies to classic properties files, not to XML files.
     *
     * @param defaultEncoding the default charset
     */
    public void setDefaultEncoding(@Nullable String defaultEncoding) {
        this.defaultEncoding = defaultEncoding;
    }

    /**
     * Return the default charset to use for parsing properties files, if any.
     *
     * @since 4.3
     */
    @Nullable
    protected String getDefaultEncoding() {
        return this.defaultEncoding;
    }

    /**
     * Set whether to fall back to the system Locale if no files for a specific
     * Locale have been found. Default is "true"; if this is turned off, the only
     * fallback will be the default file (e.g. "messages.properties" for
     * basename "messages").
     * <p>Falling back to the system Locale is the default behavior of
     * {@code java.util.ResourceBundle}. However, this is often not desirable
     * in an application server environment, where the system Locale is not relevant
     * to the application at all: set this flag to "false" in such a scenario.
     */
    public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) {
        this.fallbackToSystemLocale = fallbackToSystemLocale;
    }

    /**
     * Return whether to fall back to the system Locale if no files for a specific
     * Locale have been found.
     *
     * @since 4.3
     */
    protected boolean isFallbackToSystemLocale() {
        return this.fallbackToSystemLocale;
    }

    /**
     * Set the number of seconds to cache loaded properties files.
     * <ul>
     * <li>Default is "-1", indicating to cache forever (just like
     * {@code java.util.ResourceBundle}).
     * <li>A positive number will cache loaded properties files for the given
     * number of seconds. This is essentially the interval between refresh checks.
     * Note that a refresh attempt will first check the last-modified timestamp
     * of the file before actually reloading it; so if files don't change, this
     * interval can be set rather low, as refresh attempts will not actually reload.
     * <li>A value of "0" will check the last-modified timestamp of the file on
     * every message access. <b>Do not use this in a production environment!</b>
     * </ul>
     * <p><b>Note that depending on your ClassLoader, expiration might not work reliably
     * since the ClassLoader may hold on to a cached version of the bundle file.</b>
     * Prefer {@link ReloadableResourceBundleMessageSource} over
     * {@link ResourceBundleMessageSource} in such a scenario, in combination with
     * a non-classpath location.
     */
    public void setCacheSeconds(int cacheSeconds) {
        this.cacheMillis = (cacheSeconds * 1000);
    }

    /**
     * Set the number of milliseconds to cache loaded properties files.
     * Note that it is common to set seconds instead: {@link #setCacheSeconds}.
     * <ul>
     * <li>Default is "-1", indicating to cache forever (just like
     * {@code java.util.ResourceBundle}).
     * <li>A positive number will cache loaded properties files for the given
     * number of milliseconds. This is essentially the interval between refresh checks.
     * Note that a refresh attempt will first check the last-modified timestamp
     * of the file before actually reloading it; so if files don't change, this
     * interval can be set rather low, as refresh attempts will not actually reload.
     * <li>A value of "0" will check the last-modified timestamp of the file on
     * every message access. <b>Do not use this in a production environment!</b>
     * </ul>
     *
     * @see #setCacheSeconds
     * @since 4.3
     */
    public void setCacheMillis(long cacheMillis) {
        this.cacheMillis = cacheMillis;
    }

    /**
     * Return the number of milliseconds to cache loaded properties files.
     *
     * @since 4.3
     */
    protected long getCacheMillis() {
        return this.cacheMillis;
    }

}
